﻿// <copyright file="Viewport3D.cs" company="Axelerate Solutions S.A.">
// Copyright (c) Axelerate Solutions S.A.  All rights reserved.
// </copyright>
namespace Axelerate.Silverlight3D.Controls
{
    using System;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Ink;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Shapes;
    using Axelerate.Silverlight3D.Media.Media3D;
    using System.Windows.Markup;
    using System.Collections.Generic;

    /// <summary>
    /// Enum with the types of Light models supported by the engine.
    /// </summary>
    public enum LightModelEnum
    {
        /// <summary>
        /// No apply lighs to the render
        /// </summary>
        None,
        
        /// <summary>
        /// Use flat light by surface.
        /// </summary>
        Flat,
        
        /// <summary>
        ///  Is not currently supported is here ony for future use.
        /// </summary>
        Gouroud 
    }

    
    /// <summary>
    /// File name: Viewport3D.cs
    /// Company: Axelerate Solutions S.A.
    /// Created By: Daniel Rojas
    /// Created Date: 10/13/2008 9:11:00 h.
    /// Description: Provides a rendering surface for 3-D visual content.
    ///   <Modifications>
    ///   </Modifications>
    /// </summary>
    [ContentPropertyAttribute("Children")]
    public partial class Viewport3D : Control
    {
        #region Fields
        
        /// <summary>
        /// Render area element. Where the visual elementes goes to be added to show it to the user.
        /// </summary>
        private Canvas renderArea = new Canvas();
        internal Canvas RenderArea
        {
            get
            {
                return renderArea;
            }
        }

        private FrameworkElement rootElement { get; set; }
        private ContentPresenter renderContainer { get; set; }

        /// <summary>
        /// Identifies the Camera dependency property.
        /// </summary>
        public static readonly DependencyProperty CameraProperty =
                    DependencyProperty.Register(
                          "Camera",
                          typeof(Camera),
                          typeof(Viewport3D),
                          new PropertyMetadata(OnCameraPropertyChanged));

        public static readonly DependencyProperty ChildrenProperty =
                    DependencyProperty.Register(
                          "Children",
                          typeof(Visual3DCollection),
                          typeof(Viewport3D),
                          new PropertyMetadata(OnChildrenPropertyChanged));

        public static readonly DependencyProperty LightModelProperty =
                    DependencyProperty.Register(
                          "LightModel",
                          typeof(LightModelEnum),
                          typeof(Viewport3D),
                          new PropertyMetadata(OnLightModelPropertyChanged));

        private List<RenderEntity> renderEntitiesToRender = new List<RenderEntity>();
        private UIElementCollection currentOrderedRenderEntities;
        private List<ModelVisual3D> renderedModels = new List<ModelVisual3D>();

        private RenderEntityPool renderEntitiesPool;

        internal RenderEntityPool RenderEntitiesPool
        {
            get
            {
                return this.renderEntitiesPool;
            }
            private set
            {
                this.renderEntitiesPool = value;
            }
        }

        #endregion
        
        #region Properties

        /// <summary>
        /// Gets or sets a camera object that projects the 3-D contents of the Viewport3D to the 2-D surface of the Viewport3D. This is a dependency property.
        /// </summary>
        public Camera Camera
        {
            get
            {

                return (Camera)this.GetValue(CameraProperty);
            }
            set
            {
                value.parentViewport = this;
                this.SetValue(CameraProperty, value);
                value.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(CameraValue_PropertyChanged);
            }
        }

        void CameraValue_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            this.NotifyPropertyChanged("Camera");
        }

        /// <summary>
        /// Gets a collection of the Visual3D children of the Viewport3D. This is a dependency property.
        /// </summary>
        public Visual3DCollection Children
        {
            get
            {

                return (Visual3DCollection)this.GetValue(ChildrenProperty);
            }
        }
        
        /// <summary>
        /// Gets a value indicating whether the engine must simulate Gouroud Shading
        /// </summary>
        public bool SimulateGouroudShading
        {
            get;
            set;
        }

        /// <summary>
        /// Get or set the entity expanding offset to take advantage of the silverlight antialiasing.
        /// Due to antialiasing in Silverlight between adjacent primitives we have to actually overlap polygons slighty so the seam is not visible - however the seams will still be visible with 
        /// </summary>
        public double SeamSmoothing
        {
            get;
            set;
        }

        /// <summary>
        /// Gets a value indicating whether is nessary re-render something. Is true if anithing on the viewport or his content change. 
        /// </summary>
        internal bool IsDirty
        {
            get;
            set;
        }

        /// <summary>
        /// Gets a value indicating whether is need re-render all the models, this is necesary when the camera or the vieport inself change.
        /// </summary>
        internal bool IsViewportDirty
        {
            get;
            set;
        }

        /// <summary>
        /// Gets a value indicating whether the lights changed, this is because un change on lights require change the color of the entities but not necesary his projection
        /// </summary>
        internal bool IsLightsDirty
        {
            get;
            set;
        }


        /// <summary>
        /// Gets or sets the light model to use. bydefault is none.
        /// </summary>
        public LightModelEnum LightModel
        {
            get
            {
                return (LightModelEnum)this.GetValue(LightModelProperty);
            }
            set
            {
                this.SetValue(LightModelProperty, value);
            }
        }

        #endregion

        #region Constructor
        
        /// <summary>
        /// Initializes a new instance of the Viewport3D class.
        /// </summary>
        public Viewport3D()
            : base()
        {
            renderEntitiesPool = new RenderEntityPool(this);
            Visual3DCollection content = new Visual3DCollection();
            this.SetValue(ChildrenProperty, content);
            this.renderArea.HorizontalAlignment = HorizontalAlignment.Stretch;
            this.renderArea.VerticalAlignment = VerticalAlignment.Stretch;
            this.currentOrderedRenderEntities = this.renderArea.Children;
            content.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(content_CollectionChanged);
            CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
            this.SizeChanged += new SizeChangedEventHandler(Viewport3D_SizeChanged);
            this.DefaultStyleKey = typeof(Viewport3D);
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            this.rootElement = this.GetTemplateChild("rootElement") as FrameworkElement;
            this.renderContainer = this.GetTemplateChild("renderContainer") as ContentPresenter;
            this.renderContainer.Content = this.RenderArea;
            
        }
       

        void Viewport3D_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            this.IsDirty = true;
            this.IsViewportDirty = true; 
            //Indicate to the camera it may have to refresh itself
            if (this.Camera != null)
            {
                this.Camera.IsDirty = true;
            }
        }
        
        void content_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            this.NotifyPropertyChanged("Children");
            this.IsViewportDirty = true;
            if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
            {
                if (e.NewItems != null)
                {
                    foreach (Visual3D item in e.NewItems)
                    {
                        item.Viewport = this;
                        //item.Parent = this;
                        item.CreateRenderEntities();
                        item.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(item_PropertyChanged);
                    }
                }
            }
            else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
            {
                if (e.OldItems != null)
                {
                    foreach (Visual3D item in e.OldItems)
                    {
                        item.Viewport = null;
                        item.PropertyChanged -= new System.ComponentModel.PropertyChangedEventHandler(item_PropertyChanged);
                    }
                }
            }
            else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Reset)
            {
                if (e.OldItems != null)
                {
                    foreach (Visual3D item in e.OldItems)
                    {
                        item.Viewport = null;
                        item.PropertyChanged -= new System.ComponentModel.PropertyChangedEventHandler(item_PropertyChanged);
                    }
                }
            }
        }

        void item_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            this.NotifyPropertyChanged("Children");
            if (this.LightModel != LightModelEnum.None && sender is ModelVisual3D)
            {
                ModelVisual3D item = sender as ModelVisual3D;
                this.IsLightsDirty = this.SearchForALight(item.Content);
            }
        }

        bool SearchForALight(Model3D content)
        {
            if (content is Light)
            {
                return true;
            }
            if (content is Model3DGroup)
            {
                Model3DGroup group = content as Model3DGroup;
                foreach (Model3D item in group.Children)
                {
                    bool HasALight = SearchForALight(item);
                    if (HasALight)
                    {
                        return true;
                    }
                }

            }
            return false;
        }

        void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            if (this.Camera != null)
            {
                this.Render();
            }
        }

        //TODO: Find better method name or mechanism for rendering?
        private void Render()
        {
            // Check if a render is posibble and if is necesary
            if ((this.ActualHeight != 0) &&
                (this.ActualWidth != 0) &&
                (this.IsDirty))
            {
                // Clear the list of the visual elements
                renderEntitiesToRender.Clear();

                // Clear the list of the rendered models (can be referenced for more that one Visual3D)
                this.renderedModels.Clear();

                // Create a list of the render entities based in all his childs.
                foreach (Visual3D visual in this.Children)
                {
                    visual.Render(this, renderEntitiesToRender, renderedModels);
                }

                // Sort the entities based on his distance against the camera or the ZIndexOverride
                renderEntitiesToRender.Sort(RenderEntity.CompareDepth);

                // clear the UIElements collection that is releated to the canvas that finally will be visible
                this.currentOrderedRenderEntities.Clear();

                // Add the entities to the canvas. I have special carefully of not add a visual element that already are on the canvas.
                foreach (RenderEntity entity in this.renderEntitiesToRender)
                {
                    if (!this.currentOrderedRenderEntities.Contains(entity.Visual))
                    {
                        this.currentOrderedRenderEntities.Add(entity.Visual);
                    }
                }

                // indicate that all was succeful actualized and is not dirty anymore.
                this.IsDirty = false;
                this.IsLightsDirty = false;
                this.IsViewportDirty = false;
                
                // Reset all of the models dirty flag
                foreach (ModelVisual3D visual in renderedModels)
                {
                    foreach (Model3D model in visual.RenderedModels)
                    {
                        model.IsDirty = false;
                    }
                }
            }
        }

        #endregion

        #region Methods

        private void NotifyPropertyChanged(string PropertyChanged)
        {
            this.IsDirty = true;
            if (PropertyChanged == "Camera")
            {
                this.IsViewportDirty = true;
            }
            else if (PropertyChanged == "LightModel")
            {
                this.IsLightsDirty = true;
            }
        }

        /// <summary>
        /// Raise the Children changed event
        /// </summary>
        /// <param name="d">DependencyObject owner of the property</param>
        /// <param name="e">DependencyPropertyChangedEventArgs with the necesary information</param>
        private static void OnChildrenPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((Viewport3D)d).NotifyPropertyChanged("Children");
        }

        /// <summary>
        /// Raise the Camera changed event
        /// </summary>
        /// <param name="d">DependencyObject owner of the property</param>
        /// <param name="e">DependencyPropertyChangedEventArgs with the necesary information</param>
        private static void OnCameraPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((Viewport3D)d).NotifyPropertyChanged("Children");
        }

        /// <summary>
        /// Raise the LightModel changed event
        /// </summary>
        /// <param name="d">DependencyObject owner of the property</param>
        /// <param name="e">DependencyPropertyChangedEventArgs with the necesary information</param>
        private static void OnLightModelPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((Viewport3D)d).NotifyPropertyChanged("LightModel");
            
        }

        #endregion
    }
}
